#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>//包含上面两？
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include "locker.h"
#include "threadpool.h"
#include<signal.h>
#include "http_conn.h"
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<stdarg.h>
#include<sys/uio.h>

#define MAX_FD 65535 // 最大的文件描述符个数
#define MAX_EVENT_NUMBER 1000 // 监听的最大的事件数量

//proactor模式

//添加信号捕捉
void addsig(int sig,void(handler)(int)){
    struct sigaction sa;
    memset(&sa,'\0',sizeof(sa));
    sa.sa_handler=handler;
    sigfillset(&sa.sa_mask);//都阻塞？

    sigaction(sig,&sa,nullptr);

}

//添加文件描述符到epoll中、 删除、修改

extern void addfd(int epollfd,int fd,bool one_shot);
extern void removefd(int epollfd,int fd);
extern void modfd(int epollfd,int fd,int ev);


int main(int argc,char* argv[]){
    if(argc<=1){
        printf("按照如下格式运行: %s portnumber\n",basename(argv[0]));
        exit(-1);
    }

    //获取端口号
    int port=atoi(argv[1]);

    //对SIGPIPE信号进行处理
    addsig(SIGPIPE,SIG_IGN);

    //创建线程池，并初始化
    threadpool<http_conn>* pool=nullptr;
    try{
        pool=new threadpool<http_conn>;
    }catch(...){
        exit(-1);
    }

    //创建一个数组用于保存所有客户端信息
    http_conn *users = new http_conn[MAX_FD];

    //创建监听的套接字
    /* 
    PF_INET表示的是使用IPV4系列，使用协议时应使用这个，使用地址时要用AF_INET
    SOCK_STREAM是使用稳定的连接，即TCP
    */
   /* 创建监听socket的TCP/IP的IPV4 socket地址 */
    int listenfd=socket(PF_INET,SOCK_STREAM,0);

    //设置端口复用  注意要在绑定之前
    int reuse=1;
    /* 设置端口可以被重复使用
    int setsockopt( int socket, int level, int option_name,const void *option_value, size_t option_len);
    SO_REUSEADDR 允许端口被重复使用
    level设置为SOL_SOCKET，套接字级别上设置选项
    */
    // setsocket(listenfd,SOL_SOCKET,SO_REUSEPORT,&reuse,sizeof(reuse));
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&reuse,sizeof(reuse));

    //绑定
    struct sockaddr_in address;
    address.sin_family= AF_INET;
    address.sin_addr.s_addr=INADDR_ANY; /* INADDR_ANY：将套接字绑定到所有可用的接口 */
    address.sin_port=htons(port);// 本机字节顺序转化为网络字节顺序（大小端），host to network ulong 
    bind(listenfd,(struct sockaddr*)&address,sizeof(address));

    //监听
    /* 创建监听队列以存放待处理的客户连接，在这些客户连接被accept()之前 */
    listen(listenfd,5);//等待连接队列的最大长度???

    //创建epoll对象，事件数组，添加（文件描述符
    /* 用于存储epoll事件表中就绪事件的event数组 */
    epoll_event events[MAX_EVENT_NUMBER];

    /*
        epoll_create（max_size）创建一个epoll实例。其中nfd为epoll句柄，
        参数max_size标识这个监听的数目最大有多大，
        从Linux 2.6.8开始，max_size参数将被忽略，但必须大于零。
        epoll_create（）返回引用新epoll实例的文件描述符。
        该文件描述符用于随后的所有对epoll的调用接口。
        每创建一个epoll句柄，会占用一个fd，
        因此当不再需要时，应使用close关闭epoll_create（）返回的文件描述符，否则可能导致fd被耗尽。
        当所有文件描述符引用已关闭的epoll实例，内核将销毁该实例并释放关联的资源以供重用。
    */
    int epollfd=epoll_create(5);//任何一个值 非0？

    //将监听的文件描述符添加到epoll对象中
    /* 主线程往epoll内核事件表中注册监听socket事件，当listen到新的客户连接时，listenfd变为就绪事件 */
    addfd(epollfd,listenfd,false);//监听不要oneshot
    http_conn::m_epollfd=epollfd;

    while(1){
        /* 
            主线程调用epoll_wait等待一组文件描述符上的事件，并将当前所有就绪的epoll_event复制到events数组中
            该函数用于等待所监控文件描述符上有事件的产生，返回就绪的文件描述符个数
            events：用来存内核得到事件的集合，
            maxevents：告之内核这个events有多大，这个maxevents的值不能大于创建epoll_create()时的size，
            timeout：是超时时间
            -1：阻塞
            0：立即返回，非阻塞
            >0：指定毫秒
            返回值：成功返回有多少文件描述符就绪，时间到时返回0，出错返回-1
        */
        int num=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);//就绪列表会放到events中？
        if(num<0&&errno!=EINTR){
            //调用epoll失败
            printf("epoll failure\n");
            break;
        }
        //循环遍历事件数组
        for(int i=0;i<num;++i){
            int sockfd=events[i].data.fd;//所有需要处理的socket
            if(sockfd==listenfd){
                //有客户端连接进来
                struct sockaddr_in client_address;
                socklen_t client_addrlen=sizeof(client_address);
                //client_address会填充进来？
                /* accept()返回一个新的socket文件描述符用于send()和recv() */
                int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlen); 
                if(http_conn::m_user_count>=MAX_FD){
                    //目前连接数满了
                    //给客户端信息-服务器正忙
                    close(connfd);
                    continue;
                }
                //将新的客户数据初始化，放到数组中
                /* 并将connfd注册到内核事件表中 */
                users[connfd].init(connfd,client_address);

            }else if(events[i].events&(EPOLLRDHUP|EPOLLHUP|EPOLLERR)){
                //对方异常断开或错误等事件
                users[sockfd].close_conn();

            }else if(events[i].events&EPOLLIN){
                //读事件发生
                /* 当这一sockfd上有可读事件时，epoll_wait通知主线程。*/
                if(users[sockfd].read()){ /* 主线程从这一sockfd循环读取数据, 直到没有更多数据可读 */
                    //一次性把所有数据读完
                    // pool->append(users+sockfd);
                    /* 然后将读取到的数据封装成一个请求对象并插入请求队列 */
                    pool->append(&users[sockfd]);
                }else{
                    //读失败 or 没读到数据
                    users[sockfd].close_conn();
                }
            }else if(events[i].events&EPOLLOUT){
                //写事件发生
                /* 当这一sockfd上有可写事件时，epoll_wait通知主线程。主线程往socket上写入服务器处理客户请求的结果 */
                if(users[sockfd].write()){
                    //一次性把所有数据写完
                }else{
                    //写失败
                    users[sockfd].close_conn();
                }
            }
        }
    }

    close(epollfd);
    close(listenfd);
    delete[] users;
    delete pool;

    return 0;




}